package com.wang.lotterysystem.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.lotterysystem.common.utils.JacksonUtil;
import com.wang.lotterysystem.common.utils.RedisUtil;
import com.wang.lotterysystem.controller.enums.ActivityPrizeStatusEnum;
import com.wang.lotterysystem.controller.enums.ActivityStatusEnum;
import com.wang.lotterysystem.controller.param.DrawPrizeParam;
import com.wang.lotterysystem.controller.param.ShowWinningRecordsParam;
import com.wang.lotterysystem.dao.dataobject.*;
import com.wang.lotterysystem.dao.dataobject.activity.ActivityDO;
import com.wang.lotterysystem.dao.dataobject.activity.ActivityPrizeDO;
import com.wang.lotterysystem.dao.dataobject.prize.PrizeDO;
import com.wang.lotterysystem.dao.dataobject.user.UserDO;
import com.wang.lotterysystem.dao.mapper.*;
import com.wang.lotterysystem.service.DrawPrizeService;
import com.wang.lotterysystem.service.dto.WinningRecordDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

import static com.wang.lotterysystem.common.config.DirectRabbitConfig.EXCHANGE_NAME;
import static com.wang.lotterysystem.common.config.DirectRabbitConfig.ROUTING;

@Service
@Slf4j
public class DrawPrizeServiceImpl implements DrawPrizeService {
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ActivityMapper activityMapper;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PrizeMapper prizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;
    @Autowired
    private RedisUtil redisUtil;
    private final String WINNING_RECORDS_PREFIX = "WINNING_RECORDS_"; //需要给中奖记录缓存加上前缀,以便区分业务

    /**
     * 向mq中发送消息(mq生产者)
     * @param param 需要发送的消息内容
     */

    private static final long PRIZE_RECORD_EFFECTIVE_TIME = 60 * 60 * 24 * 2L;

    @Autowired
    private ProjectInfoAutoConfiguration projectInfoAutoConfiguration;

    @Override
    public void drawPrize(DrawPrizeParam param) throws JsonProcessingException {

        //给消息指定一个messageId(UUID)
        String messageId = UUID.randomUUID().toString();
        //消息数据: 将传入的参数序列化为String类型
        String messageData = objectMapper.writeValueAsString(param);
        //创建时间 (format:格式化)
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        //封装消息(map类型)
        Map<String,String> map = new HashMap<>();
        map.put("messageId",messageId); //设置消息id
        map.put("createTime",createTime); //设置消息创建时间
        map.put("messageData",messageData);//设置消息中的中奖信息

        //转发消息
        rabbitTemplate.convertAndSend(EXCHANGE_NAME,ROUTING,map);
        log.info("mq发送成功");
    }

    /**
     * 校验抽奖参数是否有效
     * @param param 抽奖参数
     * @return true-->有效 / false-->无效
     */
    @Override
    public Boolean checkDrawPrizeValid(DrawPrizeParam param) {
        //-----(1)校验 活动 和 活动关联的奖品 是否存在-----
        ActivityDO activityDO = activityMapper.selectById(param.getActivityId());
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByActivityAndPrizeId(param.getActivityId(),param.getPrizeId());

        if (activityDO == null || activityPrizeDO == null){
            //如果不存在 --> 返回false(抽奖参数无效)
            return false;
        }

        //-----(2)判断奖品数量是否足够-----
        if (param.getWinnerList().size() > activityPrizeDO.getPrizeAmount()){
            //如果中奖人数>现有奖品人数 --> 返回false(抽奖参数无效)
            return false;
        }

        //判断活动或者奖品是否有效
        if (activityDO.getStatus().equals(ActivityStatusEnum.COMPLETED.name())){
            //如果活动状态是completed --> 返回false
            return false;
        }
        if (activityPrizeDO.getStatus().equals(ActivityPrizeStatusEnum.COMPLETED.name())){
            //如果活动关联奖品状态是completed --> 返回false
            return false;
        }
        return true;
    }

    @Override
    public List<WinningRecordDO> saveWinningRecords(DrawPrizeParam param) throws JsonProcessingException {

        //-----1.从库中查到活动信息-----
        ActivityDO activityDO = activityMapper.selectById(param.getActivityId());
        //-----2.从库中查到活动奖品关联信息------
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByActivityAndPrizeId(
                param.getActivityId(),
                param.getPrizeId()
        );
        //-----3.从库中查到奖品信息-----
        PrizeDO prizeDO = prizeMapper.selectById(param.getPrizeId());

        //从参数中获取中奖人员id列表 winnerUserIdList
        List<Long> winnerUserIdList = new ArrayList<>();
        for (DrawPrizeParam.Winner winner :param.getWinnerList()) {
            winnerUserIdList.add(winner.getUserId());
        }
        //-----4.根据中奖人员id从库中查到中奖人员信息-----
        List<UserDO> userDOList = userMapper.selectByUserIdList(winnerUserIdList);


        //构造中奖记录对象
        List<WinningRecordDO> winningRecordDOList = new ArrayList<>();

        for (UserDO userDO : userDOList) {
            WinningRecordDO winningRecordDO = new WinningRecordDO();
            winningRecordDO.setActivityId(activityDO.getId());
            winningRecordDO.setActivityName(activityDO.getActivityName());
            winningRecordDO.setPrizeId(prizeDO.getId());
            winningRecordDO.setPrizeName(prizeDO.getName());
            winningRecordDO.setPrizeTier(activityPrizeDO.getPrizeTiers());
            winningRecordDO.setWinnerId(userDO.getId());
            winningRecordDO.setWinnerName(userDO.getUserName());
            winningRecordDO.setWinnerEmail(userDO.getEmail());
            winningRecordDO.setWinnerPhoneNumber(userDO.getPhoneNumber());
            winningRecordDO.setWinningTime(param.getWinningTime());

            winningRecordDOList.add(winningRecordDO);
        }

        //中奖记录入库
        winningRecordMapper.insert(winningRecordDOList);

        //----------[将中奖记录放入redis中]----------

        //1. 首先把奖品层面信息(单条中奖记录)存入缓存 --> key: WINNING_RECORDS_PREFIX + activityId + prizeId
        saveRecordsToCache(activityDO.getId() + "_" + prizeDO.getId(),
                winningRecordDOList,
                PRIZE_RECORD_EFFECTIVE_TIME);

        //2. 之后把活动层面信息(中奖记录列表)存入缓存 --> key: WINNING_RECORDS_PREFIX + activityId

        //(当活动完成的时候再存入缓存)
        if (activityDO.getStatus().equals(ActivityStatusEnum.COMPLETED.name())){
            //从数据库中查到中奖记录列表
            List<WinningRecordDO> recordDOList = winningRecordMapper.selectByActivityId(activityDO.getId());
            //存入缓存中
            saveRecordsToCache(String.valueOf(activityDO.getId()),
                    recordDOList,
                    PRIZE_RECORD_EFFECTIVE_TIME);
        }

        return winningRecordDOList;
    }

    @Override
    public void deleteRecords(Long activityId, Long prizeId) {
        if (activityId == null){
            log.warn("活动id为空");
            return;
        }

        //(1)删除数据库中的中奖记录
        winningRecordMapper.deleteRecords(activityId,prizeId);

        //(2)删除缓存数据
        if (prizeId != null){
            deleteCache(activityId + "_" + prizeId);
        }

        //无论奖品层面的缓存是否存在,活动层面的缓存必须删除
        deleteCache(String.valueOf(activityId));
    }

    /**
     * 获取抽奖结果
     * @param param 活动id,奖品id
     * @return
     */
    @Override
    public List<WinningRecordDTO> getRecord(ShowWinningRecordsParam param) throws JsonProcessingException {

        String key = null;
        //活动id
        Long activityId = param.getActivityId();
        //奖品id
        Long prizeId = param.getPrizeId();

        if (prizeId == null){
            //如果奖品id为空 --> str就是活动id转为String类型
            key = String.valueOf(activityId);
            //如果活动id和奖品id均不为空 --> str就是活动id和奖品id的拼接
        } else if (activityId != null && prizeId != null){
            key = activityId + "_" + prizeId;
        } else {
            //如果 活动id为空 或 两者均为空
            log.warn("活动展示参数为空:{}",param);
            return Arrays.asList();
        }

        //从缓存中查找中奖记录
        List<WinningRecordDO> recordDOList = getCache(key);
        if (recordDOList != null){
            //如果在缓存中查到 --> 直接返回
            return convertToWinningRecordDTO(recordDOList);
        }

        //如果缓存中没有查到 --> 从数据库中查
        List<WinningRecordDO> winningRecordDOList = winningRecordMapper.selectByActivityIdAndPrizeId(activityId,prizeId);

        //判断数据库中有没有查到
        if (winningRecordDOList.isEmpty()){
            log.error("要查询的活动不存在:{}",objectMapper.writeValueAsString(param));
            return null;
        }

        //如果从数据库中查到了,将其存到缓存中
        saveRecordsToCache(key,winningRecordDOList,PRIZE_RECORD_EFFECTIVE_TIME);

        //返回结果
        return convertToWinningRecordDTO(winningRecordDOList);
    }

    /**
     * 转换结果类型 (DO-->DTO)
     * @param winningRecordDOList DO类型
     * @return 返回DTO类型
     */
    private List<WinningRecordDTO> convertToWinningRecordDTO(List<WinningRecordDO> winningRecordDOList) {
        if (winningRecordDOList == null){
            log.warn("抽奖记录为空");
            return Arrays.asList();
        }

        //创建一个DTOList
        List<WinningRecordDTO> winningRecordDTOList = new ArrayList<>();

        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            //遍历每一个DO
            WinningRecordDTO winningRecordDTO = new WinningRecordDTO();
            //将DO中的属性设置到DTO中
            winningRecordDTO.setWinnerId(winningRecordDO.getWinnerId());
            winningRecordDTO.setWinnerName(winningRecordDO.getWinnerName());
            winningRecordDTO.setPrizeName(winningRecordDO.getPrizeName());
            winningRecordDTO.setPrizeTier(winningRecordDO.getPrizeTier());
            winningRecordDTO.setWinningTime(winningRecordDO.getWinningTime());

            //将当前DTO加入到DTOList中
            winningRecordDTOList.add(winningRecordDTO);
        }

        return winningRecordDTOList;
    }

    /**
     * 从缓存中获取中奖记录
     * @param key 获取缓存的字符串
     */
    private List<WinningRecordDO> getCache(String key) {
        if (key == null){
            log.warn("要获取信息的字符串为空");
            return null;
        }
        try {
            //查询中奖记录
            String record = redisUtil.get(WINNING_RECORDS_PREFIX + key);
            //校验中奖记录是否存在
            if (!StringUtils.hasText(record)){
                //如果中奖记录不存在,打印日志并返回null
                log.warn("要获取的中奖记录在缓存中不存在:{}",key);
                return null;
            }
            //如果中奖记录存在
            return JacksonUtil.readListValue(record,WinningRecordDO.class);
        } catch (Exception e){
            log.error("查询抽奖活动记录异常:{}",key);
            return null;
        }
    }

    /**
     * 删除抽奖结果的缓存
     * @param key 键
     */
    private void deleteCache(String key) {
        if (key == null){
            log.warn("需要删除的缓存key为空");
            return;
        }
        try {
            //首先判断key是否存在
            if (redisUtil.hasKey(WINNING_RECORDS_PREFIX + key)){
                //如果key存在,进行缓存删除
                redisUtil.del( WINNING_RECORDS_PREFIX + key);
            } else {
                log.warn("要删除的key不存在:{}",key);
            }
        } catch (Exception e){
            log.error("删除key异常:{}",key);
        }
    }

    /**
     * 缓存抽奖结果
     * @param key 键
     * @param winningRecordDOList 内容
     * @param prizeRecordEffectiveTime 过期时间
     */
    private void saveRecordsToCache(String key, List<WinningRecordDO> winningRecordDOList, long prizeRecordEffectiveTime) throws JsonProcessingException {
        try {
            if (key == null || winningRecordDOList == null || winningRecordDOList.isEmpty()){
                log.warn("缓存信息为空,key:{},value:{}",WINNING_RECORDS_PREFIX + key,objectMapper.writeValueAsString(winningRecordDOList));
                return;
            }
            redisUtil.set(WINNING_RECORDS_PREFIX + key,objectMapper.writeValueAsString(winningRecordDOList),prizeRecordEffectiveTime);
        }catch (Exception e){
            log.error("缓存信息失败,key:{},value:{}",key,objectMapper.writeValueAsString(winningRecordDOList));
        }
    }
}